#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

using std::cout;
using std::endl;

// 使用模板
const int gcap = 5;

template <typename T>
class blockQueue
{
public:
    blockQueue(int cap = gcap) : _cap(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_consumerCond, nullptr);
        pthread_cond_init(&_producerCond, nullptr);
    }
    // in：输入型参数
    // out：输出型参数

    // 生产数据
    void push(T &in)
    {
        pthread_mutex_lock(&_mutex);
        // 细节：一定要保证，在任何时候都是符合条件的，才进行生产，消费。 得用while 去判断
        //  if (isFull())  //error
        while (isFull()) // 1.我们只能在临界区内部，判断临界资源是否就绪！注定了我们在当前一定是持有锁的！
        {

            // 2.要让线程进行休眠等待，不能持有锁等待，否则就会造成死锁问题（这里锁只有一把）
            // 3.注定了pthread_cond_wait 有释放锁的能力
            pthread_cond_wait(&_producerCond, &_mutex); // 我休眠了，我醒来时，在哪往后执行？
            // 4.当线程醒来的时候，注定了继续从临界区内部继续向后运行，因为我是在临界区被切换走的
            // 5.注定了当线程被唤醒时，继续在pthread_cond_wait代码中阻塞处向后运行，又要重新申请锁，申请成功才会彻底返回。
            // （pthread_cond_wait函数有100行，在50行阻塞，向51行继续运行）
            // 不用担心在临界区内切走，唤醒的任何问题
        }

        _q.push(in); // 生产数据，能随意生产吗？不能当满的时候，要被阻塞。
        // 可以加策略，再去唤醒

        //  if(_q.size() >= _cap / 2)
        // 只有生产者，知道什么时候有数据，什么时候可以唤醒消费者
        pthread_cond_signal(&_consumerCond); // 唤醒也可以放在解锁的后面
        pthread_mutex_unlock(&_mutex);
        // pthread_cond_signal(&_consumerCond);
    }

    // 消费数据
    void pop(T *out)
    {
        pthread_mutex_lock(&_mutex);
        // if (isEmpty()) // error
        while (isEmpty())
        {
            pthread_cond_wait(&_consumerCond, &_mutex);
        }
        // 消费数据，能无限消费吗？不能，为空的时候不能消费要被阻塞
        *out = _q.front();
        _q.pop();

        // 加策略，去唤醒生产者
        // 只有消费者才知道，什么时候有空间，才能去唤醒生产者
        pthread_cond_signal(&_producerCond);

        pthread_mutex_unlock(&_mutex);
    }
    ~blockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_consumerCond);
        pthread_cond_destroy(&_producerCond);
    }

private:
    bool isFull() { return _q.size() == _cap; }

    bool isEmpty() { return _q.empty(); }

private:
    std::queue<T> _q;
    int _cap;               // 容量
    pthread_mutex_t _mutex; //???   ok
    // 未来呢，多线程是可以并发的访问临界资源--blockQueue的，就可能会造成，当一个线程在获取数据时，另一个线程正在写入数据
    // 这样是不安全的，会造成数据混乱，因此需要加锁保护线程安全，不能让他们同时访问临界资源。
    // 不论生产线程还是消费线程，它们访问的都是同一个队列，避免它们访问同一份资源，使用一把锁即可
    // 为什么只用一把锁？生产者一个，消费者一个，但就问你要不要访问同一个队列呢？要的，那注定了
    // 只能是同一把锁。 用同一把锁的根本原因在于，我们的生产和消费访问的是同一个queue && queue被当作整体使用


    // pthread_cond_t _cond;   //??? no ok
    // 未来呢，当缓冲区为空的时候，消费者，是不能消费，而要通知生产者进行生产；同理当缓冲区满时，生产者不能生产，要通知消费者
    // 进行消费。假设，当缓冲区为空的时候，消费者就会加锁，条件判断，释放锁，重复这样的过程，这就可能导致其他线程的饥饿，
    // 为了避免这种情况，需要使用条件变量，当条件不满足时，消费者、生产者去阻塞，而不是重复的申请锁，释放锁。
    // 再仔细分析一下，未来呢，当消费条件不满足，消费者要被阻塞，当消费条件满足时，消费者要被唤醒；当生产条件不满足，生产者要被阻塞，
    // 生产条件满足时，生产者被唤醒。
    // 所以生产者和消费者都需要一个条件变量！！！

    pthread_cond_t _consumerCond; // 消费者对应的条件变量，空 wait
    pthread_cond_t _producerCond; // 生产者对应的条件变量，满 wait
};